Popular Searches
Popular Course Categories
Popular Courses

Flutter Login & Registration UI

Flutter Login & Registration UI

Flutter Forms & User Input


Flutter Login & Registration UI


Login and Registration screens are essential parts of many Flutter applications. A Login UI allows existing users to authenticate using credentials such as email and password, while a Registration UI allows new users to create an account by entering information such as name, email, phone number, and password.


Flutter provides widgets such as Form, TextFormField, InputDecoration, FilledButton, TextButton, Card, Icon, and CheckboxListTile that can be combined to build modern and responsive authentication interfaces.




1. What Is an Authentication UI?


An authentication UI is the user interface through which users sign in, register, reset passwords, or perform other account-related actions.


Common Authentication Screens



  • Login Screen

  • Registration Screen

  • Forgot Password Screen

  • Reset Password Screen

  • Email Verification Screen

  • OTP Verification Screen

  • Profile Setup Screen




2. Login UI vs Registration UI









Login UIRegistration UI
Used by existing users.Used by new users.
Usually requires email/username and password.Usually requires name, email, password, and other account details.
May include Remember Me.May include Terms and Conditions.
Usually includes Forgot Password.Usually includes a link to Login.
Usually has a Sign In button.Usually has a Create Account/Register button.



3. Basic Structure of a Login UI


A typical Login screen can contain:



  1. Application logo or icon.

  2. Welcome heading.

  3. Email input field.

  4. Password input field.

  5. Forgot Password action.

  6. Login button.

  7. Registration navigation link.

  8. Optional social-login buttons.


Example Layout


Scaffold
 └── SafeArea
     └── SingleChildScrollView
         └── Column
             ├── Logo
             ├── Welcome Text
             ├── Email Field
             ├── Password Field
             ├── Forgot Password
             ├── Login Button
             └── Register Link



4. Basic Structure of a Registration UI


A registration screen generally contains more fields than a login screen.



  1. Application logo.

  2. Create Account heading.

  3. Full Name field.

  4. Email field.

  5. Phone Number field.

  6. Password field.

  7. Confirm Password field.

  8. Terms and Conditions checkbox.

  9. Register button.

  10. Login navigation link.




5. Important Flutter Widgets for Authentication UI















WidgetPurpose
ScaffoldProvides the basic screen structure.
SafeAreaHelps keep content away from system UI areas.
SingleChildScrollViewMakes long forms scrollable.
FormGroups and validates form fields.
TextFormFieldCreates form-aware text inputs.
InputDecorationControls labels, icons, borders, hints, and other field decoration.
FilledButtonCreates a prominent primary action button.
TextButtonCreates lightweight actions such as Forgot Password or Register.
CardCan be used to visually group authentication content.
IconAdds visual indicators to fields and actions.
CheckboxListTileUseful for Terms and Conditions acceptance.

Flutter's TextFormField integrates text input with Form and supports validation, while Material buttons such as FilledButton and TextButton provide different levels of visual emphasis for actions. :contentReference[oaicite:0]{index=0}




6. Creating the Flutter Project


Create a new Flutter application:


flutter create auth_ui_app
cd auth_ui_app
flutter run

Then replace the contents of lib/main.dart with your authentication UI implementation.




7. Creating a Basic Login Screen


The following example creates a simple login screen with email and password fields.


import 'package:flutter/material.dart';

void main() {
  runApp(const MyApp());
}

class MyApp extends StatelessWidget {
  const MyApp({super.key});

  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      debugShowCheckedModeBanner: false,
      home: const LoginPage(),
    );
  }
}

class LoginPage extends StatelessWidget {
  const LoginPage({super.key});

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      body: SafeArea(
        child: Padding(
          padding: const EdgeInsets.all(24),
          child: Column(
            mainAxisAlignment: MainAxisAlignment.center,
            children: [
              const Icon(
                Icons.lock_outline,
                size: 70,
              ),
              const SizedBox(height: 20),
              const Text(
                'Welcome Back',
                style: TextStyle(
                  fontSize: 28,
                  fontWeight: FontWeight.bold,
                ),
              ),
              const SizedBox(height: 8),
              const Text('Sign in to continue'),
              const SizedBox(height: 30),
              TextField(
                decoration: InputDecoration(
                  labelText: 'Email',
                  prefixIcon: const Icon(Icons.email_outlined),
                  border: OutlineInputBorder(
                    borderRadius: BorderRadius.circular(12),
                  ),
                ),
              ),
              const SizedBox(height: 16),
              TextField(
                obscureText: true,
                decoration: InputDecoration(
                  labelText: 'Password',
                  prefixIcon: const Icon(Icons.lock_outline),
                  border: OutlineInputBorder(
                    borderRadius: BorderRadius.circular(12),
                  ),
                ),
              ),
              const SizedBox(height: 20),
              SizedBox(
                width: double.infinity,
                child: FilledButton(
                  onPressed: () {},
                  child: const Text('Login'),
                ),
              ),
            ],
          ),
        ),
      ),
    );
  }
}




8. Using Form for Login Validation


For production-style login forms, Form and TextFormField are useful because they allow multiple fields to be validated together. Flutter's recommended validation pattern uses a GlobalKey, field validators, and validate() when the form is submitted. :contentReference[oaicite:1]{index=1}


final _formKey = GlobalKey();

Form(
  key: _formKey,
  child: Column(
    children: [
      TextFormField(
        validator: (value) {
          if (value == null || value.trim().isEmpty) {
            return 'Email is required';
          }
          return null;
        },
      ),
      TextFormField(
        obscureText: true,
        validator: (value) {
          if (value == null || value.isEmpty) {
            return 'Password is required';
          }
          return null;
        },
      ),
      FilledButton(
        onPressed: () {
          if (_formKey.currentState!.validate()) {
            print('Login form is valid');
          }
        },
        child: const Text('Login'),
      ),
    ],
  ),
)




9. Designing the Email Field


Email fields should use an appropriate keyboard type and a clear visual label.


TextFormField(
  keyboardType: TextInputType.emailAddress,
  decoration: InputDecoration(
    labelText: 'Email Address',
    hintText: '[email protected]',
    prefixIcon: const Icon(Icons.email_outlined),
    border: OutlineInputBorder(
      borderRadius: BorderRadius.circular(12),
    ),
  ),
)



10. Designing the Password Field


Password fields generally hide the entered characters. A visibility toggle can make the interface easier to use.


bool obscurePassword = true;

TextFormField(
  obscureText: obscurePassword,
  decoration: InputDecoration(
    labelText: 'Password',
    prefixIcon: const Icon(Icons.lock_outline),
    suffixIcon: IconButton(
      icon: Icon(
        obscurePassword
            ? Icons.visibility_outlined
            : Icons.visibility_off_outlined,
      ),
      onPressed: () {
        setState(() {
          obscurePassword = !obscurePassword;
        });
      },
    ),
    border: OutlineInputBorder(
      borderRadius: BorderRadius.circular(12),
    ),
  ),
)




11. Login Form Validation


Email Validation


String? validateEmail(String? value) {
  if (value == null || value.trim().isEmpty) {
    return 'Email is required';
  }

  final emailPattern =
      RegExp(r'^[^@\s]+@[^@\s]+\.[^@\s]+$');

  if (!emailPattern.hasMatch(value.trim())) {
    return 'Enter a valid email address';
  }

  return null;
}


Password Validation


String? validatePassword(String? value) {
  if (value == null || value.isEmpty) {
    return 'Password is required';
  }

  if (value.length < 8) {
    return 'Password must contain at least 8 characters';
  }

  return null;
}




12. Forgot Password UI


A Login screen commonly provides a Forgot Password action.


Align(
  alignment: Alignment.centerRight,
  child: TextButton(
    onPressed: () {
      // Navigate to forgot password screen.
    },
    child: const Text('Forgot Password?'),
  ),
)

The button should normally navigate to a password-reset flow rather than attempting to change a password directly on the login screen.




13. Registration Screen


A Registration screen collects the information needed to create a new user account.


class RegistrationPage extends StatefulWidget {
  const RegistrationPage({super.key});

  @override
  State createState() => _RegistrationPageState();
}

class _RegistrationPageState extends State {
  final _formKey = GlobalKey();

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: const Text('Create Account'),
      ),
      body: Form(
        key: _formKey,
        child: ListView(
          padding: const EdgeInsets.all(24),
          children: [
            const Text(
              'Create your account',
              style: TextStyle(
                fontSize: 28,
                fontWeight: FontWeight.bold,
              ),
            ),
            const SizedBox(height: 24),
            TextFormField(
              decoration: const InputDecoration(
                labelText: 'Full Name',
              ),
            ),
            const SizedBox(height: 16),
            TextFormField(
              decoration: const InputDecoration(
                labelText: 'Email',
              ),
            ),
            const SizedBox(height: 16),
            TextFormField(
              obscureText: true,
              decoration: const InputDecoration(
                labelText: 'Password',
              ),
            ),
            const SizedBox(height: 24),
            FilledButton(
              onPressed: () {},
              child: const Text('Create Account'),
            ),
          ],
        ),
      ),
    );
  }
}




14. Registration Fields


A registration UI can contain the following fields:











FieldPurpose
Full NameStores the user's display or personal name.
EmailUsed for account identification and communication.
Phone NumberCan be used for contact or verification.
PasswordCreates the user's authentication credential.
Confirm PasswordChecks that the password was entered consistently.
CountryCollects country information when required.
Terms CheckboxAllows users to confirm agreement with required terms.



15. Full Name Field


TextFormField(
  textCapitalization: TextCapitalization.words,
  decoration: InputDecoration(
    labelText: 'Full Name',
    prefixIcon: const Icon(Icons.person_outline),
    border: OutlineInputBorder(
      borderRadius: BorderRadius.circular(12),
    ),
  ),
  validator: (value) {
    if (value == null || value.trim().isEmpty) {
      return 'Full name is required';
    }

    if (value.trim().length < 3) {
      return 'Name must contain at least 3 characters';
    }

    return null;
  },
)




16. Phone Number Field


TextFormField(
  keyboardType: TextInputType.phone,
  decoration: InputDecoration(
    labelText: 'Phone Number',
    prefixIcon: const Icon(Icons.phone_outlined),
    border: OutlineInputBorder(
      borderRadius: BorderRadius.circular(12),
    ),
  ),
  validator: (value) {
    if (value == null || value.trim().isEmpty) {
      return 'Phone number is required';
    }

    if (!RegExp(r'^[0-9]{10}$').hasMatch(value.trim())) {
      return 'Enter a valid 10-digit phone number';
    }

    return null;
  },
)




17. Confirm Password Field


The Confirm Password field should compare its value with the password field.


final passwordController = TextEditingController();

TextFormField(
  controller: passwordController,
  obscureText: true,
  decoration: const InputDecoration(
    labelText: 'Password',
  ),
),

TextFormField(
  obscureText: true,
  decoration: const InputDecoration(
    labelText: 'Confirm Password',
  ),
  validator: (value) {
    if (value == null || value.isEmpty) {
      return 'Please confirm your password';
    }

    if (value != passwordController.text) {
      return 'Passwords do not match';
    }

    return null;
  },
)




18. Terms and Conditions


Registration forms frequently include a checkbox for required terms or consent.


bool acceptedTerms = false;

CheckboxListTile(
  contentPadding: EdgeInsets.zero,
  value: acceptedTerms,
  onChanged: (value) {
    setState(() {
      acceptedTerms = value ?? false;
    });
  },
  title: const Text(
    'I agree to the Terms and Conditions',
  ),
)


Validate the checkbox before creating the account:


if (!acceptedTerms) {
  ScaffoldMessenger.of(context).showSnackBar(
    const SnackBar(
      content: Text(
        'Please accept the Terms and Conditions',
      ),
    ),
  );
  return;
}



19. Using Card for Authentication UI


A Card can visually group authentication fields into a single surface.


Card(
  elevation: 2,
  child: Padding(
    padding: const EdgeInsets.all(24),
    child: Column(
      children: [
        const Text(
          'Login',
          style: TextStyle(
            fontSize: 26,
            fontWeight: FontWeight.bold,
          ),
        ),
        const SizedBox(height: 20),
        // Form fields
      ],
    ),
  ),
)

Flutter's Material Card widget is designed to represent related content as a Material surface and can be used to visually group authentication content. :contentReference[oaicite:2]{index=2}




20. Creating a Modern Authentication Header


Column(
  children: [
    Container(
      width: 80,
      height: 80,
      decoration: BoxDecoration(
        borderRadius: BorderRadius.circular(20),
        color: Theme.of(context).colorScheme.primaryContainer,
      ),
      child: Icon(
        Icons.lock_outline,
        size: 42,
        color: Theme.of(context).colorScheme.primary,
      ),
    ),
    const SizedBox(height: 20),
    Text(
      'Welcome Back',
      style: Theme.of(context)
          .textTheme
          .headlineMedium
          ?.copyWith(
            fontWeight: FontWeight.bold,
          ),
    ),
    const SizedBox(height: 8),
    Text(
      'Sign in to access your account',
      style: Theme.of(context).textTheme.bodyMedium,
      textAlign: TextAlign.center,
    ),
  ],
)



21. Creating a Reusable Auth Text Field


Reusable widgets help maintain consistent design across Login and Registration screens.


class AuthTextField extends StatelessWidget {
  final String label;
  final String? hint;
  final IconData icon;
  final bool obscureText;
  final TextInputType? keyboardType;
  final String? Function(String?)? validator;
  final TextEditingController? controller;

  const AuthTextField({
    super.key,
    required this.label,
    required this.icon,
    this.hint,
    this.obscureText = false,
    this.keyboardType,
    this.validator,
    this.controller,
  });

  @override
  Widget build(BuildContext context) {
    return TextFormField(
      controller: controller,
      obscureText: obscureText,
      keyboardType: keyboardType,
      validator: validator,
      decoration: InputDecoration(
        labelText: label,
        hintText: hint,
        prefixIcon: Icon(icon),
        border: OutlineInputBorder(
          borderRadius: BorderRadius.circular(12),
        ),
      ),
    );
  }
}


Using the Reusable Widget


AuthTextField(
  label: 'Email',
  icon: Icons.email_outlined,
  keyboardType: TextInputType.emailAddress,
  validator: validateEmail,
)



22. Login and Registration Navigation


Users should be able to move between Login and Registration screens.


Navigate from Login to Registration


TextButton(
  onPressed: () {
    Navigator.push(
      context,
      MaterialPageRoute(
        builder: (_) => const RegistrationPage(),
      ),
    );
  },
  child: const Text('Create an account'),
)

Navigate from Registration to Login


TextButton(
  onPressed: () {
    Navigator.pop(context);
  },
  child: const Text('Already have an account? Login'),
)



23. Complete Login UI Example


import 'package:flutter/material.dart';

class LoginPage extends StatefulWidget {
  const LoginPage({super.key});

  @override
  State createState() => _LoginPageState();
}

class _LoginPageState extends State {
  final _formKey = GlobalKey();
  final emailController = TextEditingController();
  final passwordController = TextEditingController();

  bool obscurePassword = true;
  bool isLoading = false;

  String? validateEmail(String? value) {
    if (value == null || value.trim().isEmpty) {
      return 'Email is required';
    }

    final pattern =
        RegExp(r'^[^@\s]+@[^@\s]+\.[^@\s]+$');

    if (!pattern.hasMatch(value.trim())) {
      return 'Enter a valid email address';
    }

    return null;
  }

  String? validatePassword(String? value) {
    if (value == null || value.isEmpty) {
      return 'Password is required';
    }

    if (value.length < 8) {
      return 'Password must contain at least 8 characters';
    }

    return null;
  }

  Future login() async {
    if (!_formKey.currentState!.validate()) {
      return;
    }

    setState(() {
      isLoading = true;
    });

    await Future.delayed(
      const Duration(seconds: 2),
    );

    if (!mounted) return;

    setState(() {
      isLoading = false;
    });

    ScaffoldMessenger.of(context).showSnackBar(
      const SnackBar(
        content: Text('Login request completed'),
      ),
    );
  }

  @override
  void dispose() {
    emailController.dispose();
    passwordController.dispose();
    super.dispose();
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      body: SafeArea(
        child: Center(
          child: SingleChildScrollView(
            padding: const EdgeInsets.all(24),
            child: ConstrainedBox(
              constraints: const BoxConstraints(
                maxWidth: 450,
              ),
              child: Form(
                key: _formKey,
                child: Column(
                  crossAxisAlignment:
                      CrossAxisAlignment.stretch,
                  children: [
                    Container(
                      width: 80,
                      height: 80,
                      decoration: BoxDecoration(
                        color: Theme.of(context)
                            .colorScheme
                            .primaryContainer,
                        borderRadius:
                            BorderRadius.circular(20),
                      ),
                      child: Icon(
                        Icons.lock_outline,
                        size: 42,
                        color: Theme.of(context)
                            .colorScheme
                            .primary,
                      ),
                    ),
                    const SizedBox(height: 24),
                    Text(
                      'Welcome Back',
                      textAlign: TextAlign.center,
                      style: Theme.of(context)
                          .textTheme
                          .headlineMedium
                          ?.copyWith(
                            fontWeight: FontWeight.bold,
                          ),
                    ),
                    const SizedBox(height: 8),
                    Text(
                      'Sign in to continue',
                      textAlign: TextAlign.center,
                      style: Theme.of(context)
                          .textTheme
                          .bodyMedium,
                    ),
                    const SizedBox(height: 32),
                    TextFormField(
                      controller: emailController,
                      keyboardType:
                          TextInputType.emailAddress,
                      decoration: InputDecoration(
                        labelText: 'Email',
                        prefixIcon: const Icon(
                          Icons.email_outlined,
                        ),
                        border: OutlineInputBorder(
                          borderRadius:
                              BorderRadius.circular(12),
                        ),
                      ),
                      validator: validateEmail,
                    ),
                    const SizedBox(height: 16),
                    TextFormField(
                      controller: passwordController,
                      obscureText: obscurePassword,
                      decoration: InputDecoration(
                        labelText: 'Password',
                        prefixIcon: const Icon(
                          Icons.lock_outline,
                        ),
                        suffixIcon: IconButton(
                          onPressed: () {
                            setState(() {
                              obscurePassword =
                                  !obscurePassword;
                            });
                          },
                          icon: Icon(
                            obscurePassword
                                ? Icons.visibility_outlined
                                : Icons.visibility_off_outlined,
                          ),
                        ),
                        border: OutlineInputBorder(
                          borderRadius:
                              BorderRadius.circular(12),
                        ),
                      ),
                      validator: validatePassword,
                    ),
                    Align(
                      alignment: Alignment.centerRight,
                      child: TextButton(
                        onPressed: () {},
                        child: const Text(
                          'Forgot Password?',
                        ),
                      ),
                    ),
                    const SizedBox(height: 8),
                    FilledButton(
                      onPressed:
                          isLoading ? null : login,
                      child: isLoading
                          ? const SizedBox(
                              height: 20,
                              width: 20,
                              child:
                                  CircularProgressIndicator(
                                strokeWidth: 2,
                              ),
                            )
                          : const Text('Login'),
                    ),
                    const SizedBox(height: 20),
                    Row(
                      mainAxisAlignment:
                          MainAxisAlignment.center,
                      children: [
                        const Text(
                          "Don't have an account?",
                        ),
                        TextButton(
                          onPressed: () {},
                          child: const Text('Register'),
                        ),
                      ],
                    ),
                  ],
                ),
              ),
            ),
          ),
        ),
      ),
    );
  }
}




24. Complete Registration UI Example


import 'package:flutter/material.dart';

class RegistrationPage extends StatefulWidget {
  const RegistrationPage({super.key});

  @override
  State createState() => _RegistrationPageState();
}

class _RegistrationPageState
    extends State {
  final _formKey = GlobalKey();

  final nameController = TextEditingController();
  final emailController = TextEditingController();
  final phoneController = TextEditingController();
  final passwordController = TextEditingController();
  final confirmPasswordController =
      TextEditingController();

  bool obscurePassword = true;
  bool obscureConfirmPassword = true;
  bool acceptedTerms = false;
  bool isLoading = false;

  String? validateName(String? value) {
    if (value == null || value.trim().isEmpty) {
      return 'Full name is required';
    }

    if (value.trim().length < 3) {
      return 'Name must contain at least 3 characters';
    }

    return null;
  }

  String? validateEmail(String? value) {
    if (value == null || value.trim().isEmpty) {
      return 'Email is required';
    }

    final pattern =
        RegExp(r'^[^@\s]+@[^@\s]+\.[^@\s]+$');

    if (!pattern.hasMatch(value.trim())) {
      return 'Enter a valid email address';
    }

    return null;
  }

  String? validatePhone(String? value) {
    if (value == null || value.trim().isEmpty) {
      return 'Phone number is required';
    }

    if (!RegExp(r'^[0-9]{10}$')
        .hasMatch(value.trim())) {
      return 'Enter a valid 10-digit phone number';
    }

    return null;
  }

  String? validatePassword(String? value) {
    if (value == null || value.isEmpty) {
      return 'Password is required';
    }

    if (value.length < 8) {
      return 'Password must contain at least 8 characters';
    }

    return null;
  }

  Future register() async {
    if (!_formKey.currentState!.validate()) {
      return;
    }

    if (!acceptedTerms) {
      ScaffoldMessenger.of(context).showSnackBar(
        const SnackBar(
          content: Text(
            'Please accept the Terms and Conditions',
          ),
        ),
      );
      return;
    }

    setState(() {
      isLoading = true;
    });

    await Future.delayed(
      const Duration(seconds: 2),
    );

    if (!mounted) return;

    setState(() {
      isLoading = false;
    });

    ScaffoldMessenger.of(context).showSnackBar(
      const SnackBar(
        content: Text(
          'Registration request completed',
        ),
      ),
    );
  }

  @override
  void dispose() {
    nameController.dispose();
    emailController.dispose();
    phoneController.dispose();
    passwordController.dispose();
    confirmPasswordController.dispose();
    super.dispose();
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      body: SafeArea(
        child: Center(
          child: SingleChildScrollView(
            padding: const EdgeInsets.all(24),
            child: ConstrainedBox(
              constraints: const BoxConstraints(
                maxWidth: 500,
              ),
              child: Form(
                key: _formKey,
                child: Column(
                  crossAxisAlignment:
                      CrossAxisAlignment.stretch,
                  children: [
                    const Icon(
                      Icons.person_add_alt_1,
                      size: 70,
                    ),
                    const SizedBox(height: 20),
                    Text(
                      'Create Account',
                      textAlign: TextAlign.center,
                      style: Theme.of(context)
                          .textTheme
                          .headlineMedium
                          ?.copyWith(
                            fontWeight: FontWeight.bold,
                          ),
                    ),
                    const SizedBox(height: 8),
                    Text(
                      'Register to get started',
                      textAlign: TextAlign.center,
                      style: Theme.of(context)
                          .textTheme
                          .bodyMedium,
                    ),
                    const SizedBox(height: 30),
                    TextFormField(
                      controller: nameController,
                      textCapitalization:
                          TextCapitalization.words,
                      decoration: InputDecoration(
                        labelText: 'Full Name',
                        prefixIcon: const Icon(
                          Icons.person_outline,
                        ),
                        border: OutlineInputBorder(
                          borderRadius:
                              BorderRadius.circular(12),
                        ),
                      ),
                      validator: validateName,
                    ),
                    const SizedBox(height: 16),
                    TextFormField(
                      controller: emailController,
                      keyboardType:
                          TextInputType.emailAddress,
                      decoration: InputDecoration(
                        labelText: 'Email',
                        prefixIcon: const Icon(
                          Icons.email_outlined,
                        ),
                        border: OutlineInputBorder(
                          borderRadius:
                              BorderRadius.circular(12),
                        ),
                      ),
                      validator: validateEmail,
                    ),
                    const SizedBox(height: 16),
                    TextFormField(
                      controller: phoneController,
                      keyboardType: TextInputType.phone,
                      decoration: InputDecoration(
                        labelText: 'Phone Number',
                        prefixIcon: const Icon(
                          Icons.phone_outlined,
                        ),
                        border: OutlineInputBorder(
                          borderRadius:
                              BorderRadius.circular(12),
                        ),
                      ),
                      validator: validatePhone,
                    ),
                    const SizedBox(height: 16),
                    TextFormField(
                      controller: passwordController,
                      obscureText: obscurePassword,
                      decoration: InputDecoration(
                        labelText: 'Password',
                        prefixIcon: const Icon(
                          Icons.lock_outline,
                        ),
                        suffixIcon: IconButton(
                          onPressed: () {
                            setState(() {
                              obscurePassword =
                                  !obscurePassword;
                            });
                          },
                          icon: Icon(
                            obscurePassword
                                ? Icons.visibility_outlined
                                : Icons.visibility_off_outlined,
                          ),
                        ),
                        border: OutlineInputBorder(
                          borderRadius:
                              BorderRadius.circular(12),
                        ),
                      ),
                      validator: validatePassword,
                    ),
                    const SizedBox(height: 16),
                    TextFormField(
                      controller:
                          confirmPasswordController,
                      obscureText:
                          obscureConfirmPassword,
                      decoration: InputDecoration(
                        labelText: 'Confirm Password',
                        prefixIcon: const Icon(
                          Icons.lock_reset_outlined,
                        ),
                        suffixIcon: IconButton(
                          onPressed: () {
                            setState(() {
                              obscureConfirmPassword =
                                  !obscureConfirmPassword;
                            });
                          },
                          icon: Icon(
                            obscureConfirmPassword
                                ? Icons.visibility_outlined
                                : Icons.visibility_off_outlined,
                          ),
                        ),
                        border: OutlineInputBorder(
                          borderRadius:
                              BorderRadius.circular(12),
                        ),
                      ),
                      validator: (value) {
                        if (value == null ||
                            value.isEmpty) {
                          return 'Please confirm your password';
                        }

                        if (value !=
                            passwordController.text) {
                          return 'Passwords do not match';
                        }

                        return null;
                      },
                    ),
                    const SizedBox(height: 10),
                    CheckboxListTile(
                      contentPadding: EdgeInsets.zero,
                      value: acceptedTerms,
                      onChanged: (value) {
                        setState(() {
                          acceptedTerms =
                              value ?? false;
                        });
                      },
                      title: const Text(
                        'I agree to the Terms and Conditions',
                      ),
                    ),
                    const SizedBox(height: 16),
                    FilledButton(
                      onPressed:
                          isLoading ? null : register,
                      child: isLoading
                          ? const SizedBox(
                              height: 20,
                              width: 20,
                              child:
                                  CircularProgressIndicator(
                                strokeWidth: 2,
                              ),
                            )
                          : const Text(
                              'Create Account',
                            ),
                    ),
                    const SizedBox(height: 20),
                    Row(
                      mainAxisAlignment:
                          MainAxisAlignment.center,
                      children: [
                        const Text(
                          'Already have an account?',
                        ),
                        TextButton(
                          onPressed: () {
                            Navigator.pop(context);
                          },
                          child: const Text('Login'),
                        ),
                      ],
                    ),
                  ],
                ),
              ),
            ),
          ),
        ),
      ),
    );
  }
}




25. Login and Registration in a Single Application


A simple authentication application can use separate screens for Login and Registration.


MaterialApp(
  debugShowCheckedModeBanner: false,
  initialRoute: '/login',
  routes: {
    '/login': (context) => const LoginPage(),
    '/register': (context) => const RegistrationPage(),
  },
)

Navigate to Registration


Navigator.pushNamed(context, '/register');

Navigate to Login


Navigator.pushNamed(context, '/login');



26. Responsive Login and Registration UI


Authentication screens should work on mobile phones, tablets, and larger displays.


Useful Responsive Techniques



  • Use SafeArea.

  • Use SingleChildScrollView for forms.

  • Use ConstrainedBox to limit excessive width on large screens.

  • Use MediaQuery when screen dimensions affect layout decisions.

  • Use flexible layouts instead of fixed widths whenever possible.

  • Keep form fields readable on large screens.


Responsive Container Example


Center(
  child: SingleChildScrollView(
    padding: const EdgeInsets.all(24),
    child: ConstrainedBox(
      constraints: const BoxConstraints(
        maxWidth: 500,
      ),
      child: Form(
        child: Column(
          children: [
            // Authentication fields
          ],
        ),
      ),
    ),
  ),
)



27. Handling Keyboard Overflow


Authentication forms can become hidden when the software keyboard appears, especially on smaller devices. Wrapping the content in SingleChildScrollView is a common solution.


Scaffold(
  body: SafeArea(
    child: SingleChildScrollView(
      padding: const EdgeInsets.all(24),
      child: Form(
        child: Column(
          children: [
            // Login or registration fields
          ],
        ),
      ),
    ),
  ),
)



28. Improving the Login Button


A primary login action should be visually prominent. Flutter's Material 3 button system includes FilledButton for filled primary actions. :contentReference[oaicite:3]{index=3}


SizedBox(
  width: double.infinity,
  child: FilledButton(
    onPressed: login,
    child: const Text('Login'),
  ),
)

Custom Button Style


FilledButton(
  style: FilledButton.styleFrom(
    minimumSize: const Size.fromHeight(52),
    shape: RoundedRectangleBorder(
      borderRadius: BorderRadius.circular(12),
    ),
  ),
  onPressed: login,
  child: const Text('Login'),
)



29. Loading State During Authentication


When an application communicates with an authentication API, the button can display a loading state while the request is in progress.


bool isLoading = false;

FilledButton(
  onPressed: isLoading ? null : login,
  child: isLoading
      ? const SizedBox(
          height: 20,
          width: 20,
          child: CircularProgressIndicator(
            strokeWidth: 2,
          ),
        )
      : const Text('Login'),
)


Why Loading State Is Important



  • Provides visual feedback.

  • Prevents accidental repeated submissions.

  • Makes network operations easier to understand.

  • Improves the overall user experience.




30. Showing Authentication Errors


After connecting the UI to a backend, authentication can fail because of incorrect credentials, unavailable services, expired sessions, or other server-side conditions.


void showLoginError(String message) {
  ScaffoldMessenger.of(context).showSnackBar(
    SnackBar(
      content: Text(message),
    ),
  );
}

For field-specific errors, display the message near the relevant field. For general authentication errors, a SnackBar, dialog, or inline message can be used depending on the design.




31. Login UI with Remember Me


A Remember Me checkbox can be added when the authentication architecture supports persistent sessions.


bool rememberMe = false;

CheckboxListTile(
  contentPadding: EdgeInsets.zero,
  value: rememberMe,
  onChanged: (value) {
    setState(() {
      rememberMe = value ?? false;
    });
  },
  title: const Text('Remember me'),
)


The actual persistence of authentication state should be handled using an appropriate secure authentication/session architecture rather than simply storing a password in plain text.




32. Social Login UI


Some applications provide additional authentication methods such as Google, Apple, or other identity providers. The UI can contain separate buttons for these actions.


OutlinedButton.icon(
  onPressed: () {
    // Start social authentication.
  },
  icon: const Icon(Icons.login),
  label: const Text('Continue with Provider'),
)

The actual authentication flow requires the appropriate backend or authentication SDK. The UI button alone does not authenticate a user.




33. Creating a Divider Between Login Methods


Row(
  children: [
    const Expanded(child: Divider()),
    Padding(
      padding: const EdgeInsets.symmetric(
        horizontal: 12,
      ),
      child: Text(
        'OR',
        style: Theme.of(context)
            .textTheme
            .bodySmall,
      ),
    ),
    const Expanded(child: Divider()),
  ],
)



34. Authentication UI Color and Theme


Instead of hard-coding every color inside individual widgets, define the application's visual identity through ThemeData.


MaterialApp(
  theme: ThemeData(
    useMaterial3: true,
    colorSchemeSeed: Colors.indigo,
    inputDecorationTheme:
        const InputDecorationTheme(
      border: OutlineInputBorder(),
    ),
  ),
  home: const LoginPage(),
)

A centralized theme makes the Login and Registration screens visually consistent and easier to maintain.




35. Creating a Reusable Authentication Button


class AuthButton extends StatelessWidget {
  final String label;
  final VoidCallback? onPressed;
  final bool isLoading;

  const AuthButton({
    super.key,
    required this.label,
    required this.onPressed,
    this.isLoading = false,
  });

  @override
  Widget build(BuildContext context) {
    return SizedBox(
      width: double.infinity,
      child: FilledButton(
        onPressed: isLoading ? null : onPressed,
        child: isLoading
            ? const SizedBox(
                height: 20,
                width: 20,
                child: CircularProgressIndicator(
                  strokeWidth: 2,
                ),
              )
            : Text(label),
      ),
    );
  }
}


Usage


AuthButton(
  label: 'Login',
  onPressed: login,
  isLoading: isLoading,
)



36. Separating UI and Authentication Logic


For small learning projects, authentication logic can be placed inside the screen. For larger applications, separating UI, validation, authentication services, and state management makes the application easier to maintain.


Example Project Structure


lib/
├── main.dart
├── screens/
│   ├── login_page.dart
│   ├── registration_page.dart
│   └── forgot_password_page.dart
├── widgets/
│   ├── auth_text_field.dart
│   └── auth_button.dart
├── services/
│   └── auth_service.dart
├── models/
│   └── user_model.dart
└── validators/
    └── form_validators.dart



37. Authentication Service Concept


The UI should collect and validate input, while an authentication service can handle communication with the backend or authentication provider.


class AuthService {
  Future login(
    String email,
    String password,
  ) async {
    // Call authentication API here.
    return true;
  }

  Future register(
    String name,
    String email,
    String password,
  ) async {
    // Call registration API here.
    return true;
  }
}




38. Login Flow



  1. User opens the Login screen.

  2. User enters email and password.

  3. Flutter validates the fields.

  4. If validation fails, errors are displayed.

  5. If validation succeeds, the app starts the authentication request.

  6. A loading state is displayed.

  7. The authentication service sends the credentials to the backend.

  8. The backend verifies the credentials.

  9. On success, the application navigates to the appropriate authenticated screen.

  10. On failure, an appropriate error is displayed.




39. Registration Flow



  1. User opens the Registration screen.

  2. User enters account information.

  3. Flutter validates all fields.

  4. Terms and Conditions requirements are checked when applicable.

  5. The application displays a loading state.

  6. Registration information is sent to the authentication backend.

  7. The backend validates and creates the account if the request is acceptable.

  8. The application handles success or failure.

  9. The user may be redirected to Login, verification, or the authenticated area according to the application's flow.




40. Common Login UI Mistakes



  • Using very small input fields.

  • Not providing useful validation messages.

  • Forgetting the Forgot Password action when required.

  • Not handling loading states.

  • Allowing repeated login requests.

  • Hard-coding authentication results into the UI.

  • Logging passwords or other sensitive credentials.

  • Creating a layout that cannot scroll when the keyboard opens.

  • Using inconsistent spacing and typography.

  • Making the Login button difficult to identify.




41. Common Registration UI Mistakes



  • Collecting unnecessary information.

  • Not validating email addresses.

  • Not checking password confirmation.

  • Not providing clear password requirements.

  • Ignoring keyboard overflow.

  • Not showing registration errors.

  • Allowing duplicate submissions.

  • Storing passwords insecurely.

  • Not handling backend validation errors.

  • Not providing a clear path back to Login.




42. Login and Registration UI Best Practices



  • Keep the authentication UI simple and focused.

  • Use clear labels for every field.

  • Use appropriate keyboard types.

  • Use TextFormField with validators for form input.

  • Use SingleChildScrollView for long forms.

  • Use consistent spacing between fields.

  • Provide meaningful validation messages.

  • Provide password visibility controls when appropriate.

  • Show a loading state during network operations.

  • Prevent duplicate submissions.

  • Do not display or log passwords.

  • Use secure authentication and session-management practices.

  • Perform server-side validation in addition to client-side validation.

  • Keep UI components reusable.

  • Use a centralized theme for consistent design.

  • Make the layout usable across different screen sizes.




43. Accessibility Considerations


Authentication screens should be accessible to as many users as possible.



  • Use descriptive labels.

  • Ensure adequate contrast between text and background.

  • Provide sufficiently large interactive controls.

  • Do not rely only on color to communicate errors.

  • Use meaningful error messages.

  • Maintain a logical focus order.

  • Test the screen with different text scaling settings.

  • Ensure buttons and fields have meaningful semantic labels where necessary.




44. Practical Project: Complete Authentication UI


Create a Flutter authentication application with the following screens:



  1. Login Screen

  2. Registration Screen

  3. Forgot Password Screen

  4. Home Screen


Login Screen Requirements



  • Email field.

  • Password field.

  • Password visibility toggle.

  • Email validation.

  • Password validation.

  • Forgot Password button.

  • Login button.

  • Registration navigation.

  • Loading state.


Registration Screen Requirements



  • Full Name.

  • Email.

  • Phone Number.

  • Password.

  • Confirm Password.

  • Terms and Conditions checkbox.

  • Password visibility toggle.

  • Registration validation.

  • Loading state.

  • Navigation to Login.




45. Interview Questions


Q1. What widgets are commonly used to create Login and Registration UI in Flutter?


Common widgets include Form, TextFormField, InputDecoration, FilledButton, TextButton, CheckboxListTile, Card, Column, Row, SafeArea, and SingleChildScrollView.


Q2. Why use TextFormField instead of TextField for authentication forms?


TextFormField integrates with Form and provides form-field validation functionality. :contentReference[oaicite:4]{index=4}


Q3. How do you validate a Login form?


Create a Form with a GlobalKey, add validators to the fields, and call validate() when the user submits the form. :contentReference[oaicite:5]{index=5}


Q4. How do you hide a password in Flutter?


Set obscureText: true on the text field. A state variable can be used to toggle the value when the user presses a visibility icon.


Q5. Why should authentication forms be scrollable?


Scrollable layouts help prevent fields and buttons from being hidden when the keyboard appears or when the device has a smaller display.


Q6. Why should controllers be disposed?


TextEditingController objects should be disposed when they are no longer needed to release their associated resources. :contentReference[oaicite:6]{index=6}


Q7. What is the difference between Login and Registration?


Login authenticates an existing account, while Registration collects information needed to create a new account.




46. Quick Revision



  • Login is used for existing users.

  • Registration is used to create new accounts.

  • Use Form to organize authentication fields.

  • Use TextFormField for validated form input.

  • Use GlobalKey to access form state.

  • Use validator for email, password, phone, and other input validation.

  • Use obscureText for password fields.

  • Use a visibility toggle when appropriate.

  • Use SingleChildScrollView to handle smaller screens and keyboard appearance.

  • Use FilledButton for important primary actions.

  • Use TextButton for secondary actions such as Forgot Password and navigation.

  • Show loading states during authentication requests.

  • Prevent duplicate submissions.

  • Do not log or expose passwords.

  • Use secure backend authentication and server-side validation.

  • Keep authentication widgets reusable and responsive.




47. Official Flutter Resources





48. JustAcademy Flutter Training Resources





Key Takeaways


A well-designed Flutter Login and Registration UI combines clean Material Design components, structured forms, input validation, responsive layouts, password visibility controls, loading states, and clear navigation. Form and TextFormField provide the foundation for validated authentication forms, while reusable widgets and centralized themes help keep the application consistent and maintainable.


whatsapp